package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

/**
 * 聊天室服务端
 */
public class Server {
    /*
    ServerSocket是运行在服务端的，主要有两个作用：
    1.向系统申请服务端口，客户端就是通过这个端口与服务端建立连接
    2.监听服务端口，一但一个客户端连接了就会立即返回一个Socket，
    通过这个Socket与客户端对等交互

    如果我们将Socket比喻为"电话"，nameServerSocket相当于是"总机"
     */
    private ServerSocket serverSocket;

    // private PrintWriter[] allOut = {};
    //private Collection<PrintWriter> allOut = new ArrayList<>();
    private List<PrintWriter> allOut = new ArrayList<>();
    public Server() {
        try {
            /*
            实例化ServerSocket的同时指定服务端口，客户端就是
            通过这个端口与服务端建立连接的，该端口不能与当前系统
            其他应用程序申请的端口一致，否则会抛出异常：
            java.net.bindException:address already in use
             */
            System.out.println("正在启动服务端...");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {

        try {
            /*
            ServerSocket提供的方法：
            Socket accept()
            该方法是一个堵塞方法，调用后程序卡"卡住"，开始等待客户端的
            链接，一旦一个客户端建立连接，此时该方法会立即返回一个Socket，通过这个Socket就可以与该客户端交互了
            相当于是"接电话"操作
             */
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                //启动一个线程来处理该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();

    }

    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;

        public ClientHandler(Socket socket) {
            this.socket = socket;
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);

                /*
                选取对象的原则：多个线看到的锁对象必须是同一个
                通常我们可以指定临界资源作为锁对象

                但是这里多个线程抢的临界资源是allOut，这里不行的原因是：
                同步块中的操作包含对数组的扩容，而扩容会导致allOut指向
                别的对象，那么就等于说锁对象一直在发生改变
             */
                /*synchronized (ClientHandler.class) {
                    allOut.add(pw);
                    //将当前输出流存入共享数据allout中
                allOut = Arrays.copyOf(allOut,allOut.length+1);
                allOut[allOut.length-1] = pw;
                }*/
                // sendMessage(host+"上线了，当前在线人数:"+allOut.length);
                //并发安全的集合多线程调用add时会同步操作
            allOut.add(pw);
                sendMessage(host + "上线了，当前在线人数:" + allOut.size());
                String line;

                while ((line = br.readLine()) != null) {
                    System.out.println(host + "说" + line);
                    for (PrintWriter pwr : allOut) {
                        pwr.println(host + "说" + line);
                    }
                    /*for(int i = 0; i<allOut.length;i++){
                    allOut[i].println(host+"说"+line);
                    }*/
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开连接后的操作
                //将当前客户端的输出流从数组allOut中删除
               /* synchronized (ClientHandler.class) {
                    for (PrintWriter pwr : allOut) {
                        if (pwr == pw) {
                            allOut.remove(pwr);
                            break;
                        }
                    }
                 for (int i = 0; i < allOut.length; i++) {
                     if (allOut[i] == pw) {
                         allOut[i] = allOut[allOut.length - 1];
                         allOut = Arrays.copyOf(allOut, allOut.length - 1);
                         break;
                     }
                }*/
            }
            allOut.remove(pw);
            sendMessage(host + "下线了，当前在线人数:" + allOut.size());
            //sendMessage(host+"下线了,当前在线人数:"+allOut.length);
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将消息发给所有客户端
     *
     * @param message
     */
    public void sendMessage(String message) {
       /* synchronized (ClientHandler.class) {
            for (PrintWriter pwr : allOut) {
                pwr.println(message);
                /*for(int i = 0;i<allOut.length;i++){
                    allOut[i].println(message);
                }
            }
        }*/
        allOut.forEach(pw->pw.println(message));
    }
}
